#include "planner/physical_operators.h"

#include <memory>
#include <vector>

#include "common/hash_util.h"
#include "common/node_utils.h"

extern "C" {
#include <postgres.h>

#include <nodes/nodes.h>
#include <nodes/parsenodes.h>
#include <nodes/primnodes.h>
}

namespace optimizer {

//===--------------------------------------------------------------------===//
// PhysicalSeqScan
//===--------------------------------------------------------------------===//
std::shared_ptr<Operator> PhysicalSeqScan::Copy() const {
  return std::make_shared<PhysicalSeqScan>(rte_);
}

Oid PhysicalSeqScan::GetTableOID() const {
  return rte_ != nullptr ? rte_->relid : InvalidOid;
}

bool PhysicalSeqScan::operator==(const Operator &r) const {
  if (r.GetOpType() != OperatorType::PhysicalSeqScan)
    return false;
  const auto &node = r.CastTo<PhysicalSeqScan>();
  return GetTableOID() == node.GetTableOID();
}

hash_t PhysicalSeqScan::Hash() const {
  hash_t hash = Operator::Hash();
  hash = HashUtil::CombineHashes(hash, HashUtil::Hash(GetTableOID()));
  return hash;
}

//===--------------------------------------------------------------------===//
// OrderBy
//===--------------------------------------------------------------------===//
std::shared_ptr<Operator> OrderBy::Copy() const {
  return std::make_shared<OrderBy>();
}

bool OrderBy::operator==(const Operator &r) const {
  return (r.GetOpType() == OperatorType::OrderBy);
  // Again, there isn't any internal data so I guess we're always equal!
}

hash_t OrderBy::Hash() const {
  hash_t hash = Operator::Hash();
  // I guess every OrderBy object hashes to the same thing?
  return hash;
}

//===--------------------------------------------------------------------===//
// PhysicalLimit
//===--------------------------------------------------------------------===//
std::shared_ptr<Operator> Limit::Copy() const {
  std::vector<optimizer::OrderByOrderingType> sort_directions{sort_directions_};
  return std::make_shared<Limit>();
}

bool Limit::operator==(const Operator &r) const {
  if (r.GetOpType() != OperatorType::Limit)
    return false;
  const auto &node = r.CastTo<Limit>();
  if (offset_ != node.offset_)
    return false;
  if (limit_ != node.limit_)
    return false;
  if (sort_exprs_ != node.sort_exprs_)
    return false;
  if (sort_directions_ != node.sort_directions_)
    return false;
  return (true);
}

hash_t Limit::Hash() const {
  hash_t hash = Operator::Hash();
  hash = HashUtil::CombineHashes(hash, HashUtil::Hash(offset_));
  hash = HashUtil::CombineHashes(hash, HashUtil::Hash(limit_));
  hash = HashUtil::CombineHashes(hash, NodeUtils::ExprHash(sort_exprs_));
  hash = HashUtil::CombineHashInRange(hash, sort_directions_.begin(), sort_directions_.end());
  return hash;
}

}  // namespace optimizer
